﻿package api;

import flash.net.URLRequest;
import flash.net.URLRequestMethod;
import flash.net.URLLoader;
import flash.net.URLLoaderDataFormat;
import flash.net.URLVariables;

import flash.events.IOErrorEvent;
import flash.events.SecurityErrorEvent;
import flash.events.Event;

typedef Request = {
	method: String,
	params: Dynamic,
	onComplete: Xml -> Void,
	onError: String -> Void	
}

class RequestManager {
	public static var API_SERVER_URL: String = "http://api.vkontakte.ru/api.php";
	
    private static var CHECK_INTERVAL = 50; // 50 miliseconds
	private static var REQUEST_INTERVAL = 1000; // 1 second
	
	private var queue : List<Request>;
	private var timeAfterLastRequest: Int;
	private var requestIndex: Int;

	private var apiId: Int;
    private var apiSecret: String;
    private var viewerId: Int;
    private var testMode: Bool;
    private var requestParams: Array<Dynamic>;
    
	public function new(apiId: Int, apiSecret: String, viewerId: Int, testMode: Bool = false) {
		this.apiId = apiId;
		this.apiSecret = apiSecret;
		this.viewerId = viewerId;
		this.testMode  = testMode;
    	this.queue = new List<Request>();
		this.timeAfterLastRequest = 0;
		this.requestIndex = 0;
		
		var timer = new haxe.Timer(CHECK_INTERVAL);
		timer.run = this.sendNext;
	}	
	
	public function add(request: Request) {
		queue.add(request);
	}
	
	private function sendNext() {
		if (queue.length != 0) {
			var toSend = false;
		
			if (timeAfterLastRequest >= REQUEST_INTERVAL) {
				timeAfterLastRequest = 0;
				requestIndex = 0;
				toSend = true;
			} else {
				timeAfterLastRequest += CHECK_INTERVAL;
				if (requestIndex < 2) {
					toSend = true;
					requestIndex++;
				}				
			}
			if (toSend) {
				var request = queue.pop();
				send(request);
				sendNext();
			}
		} else {
			timeAfterLastRequest += CHECK_INTERVAL;
		}
	}
	
	private function send(req: Request) {
		var requestParams = new URLVariables();
		if (req.params != null) {
			for (field in Reflect.fields(req.params)) {
				// if param is null skip it
				if (Reflect.field(req.params, field) != null) {
					Reflect.setField(requestParams, field, Reflect.field(req.params, field));
				}
			}
		}
		Reflect.setField(requestParams, "method", req.method);
		Reflect.setField(requestParams, "api_id", apiId);
		Reflect.setField(requestParams, "format", "XML");
		Reflect.setField(requestParams, "v", "2.0");
		if (testMode) {
			Reflect.setField(requestParams, "test_mode", "1");
		}
		Reflect.setField(requestParams, "sig", generateSignature(requestParams));
		
		var request = new URLRequest(API_SERVER_URL);
		request.method = URLRequestMethod.POST;
		request.data = requestParams;
		//trace("Request data: " + request.data);
	  
		var loader = new URLLoader();
		loader.dataFormat = URLLoaderDataFormat.TEXT;
		if (req.onError != null) {
			loader.addEventListener(IOErrorEvent.IO_ERROR, function(e: Dynamic) {
				req.onError("Ошибка подключения к серверу:\n" + e);
			});
			loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function(e: Dynamic) {
				req.onError("Ошибка системы безопасности на сервере:\n" + e);
			});
		}
	  
		if (req.onComplete != null) {
			loader.addEventListener(Event.COMPLETE, function(e:Event){
				var data = loader.data;
				req.onComplete(Xml.parse("" + data));
			});
		}
		try {
			loader.load(request);
		} catch (e: Dynamic) {
			if (req.onError != null) {
				req.onError("Не удалось подключиться к серверу.\n" + e);
			}
		}
    }

    private function generateSignature(requestParams: URLVariables): String {
		var signature = "";
		var sortedArray = new Array<String>();
		for (field in Reflect.fields(requestParams)) {
			sortedArray.push(field + "=" + Reflect.field(requestParams, field));
		}
		sortedArray.sort(this.compare);
		
		// Note: make sure that the signature parameter is not already included in
		//       request_params array.
		for (key in sortedArray) {
			signature += Utf8Encode(key);
		}
		signature = viewerId + signature + apiSecret;
		//trace("Signature: " + signature);
		return haxe.Md5.encode(signature);
    }
	
	private function compare(s1: String, s2: String): Int {
		var i = 0;
		
		while (i < s1.length && i < s2.length) {
			if (s1.charAt(i) > s2.charAt(i)) {
				return 1;
			} else if (s1.charAt(i) < s2.charAt(i)) {
				return -1;
			} else {
				i++;
			}
		}
		
		if (i == s1.length) {
			if (i == s2.length) {
				return 0;
			} else {
				return -1;
			}
		} else {
			return 1;
		}
	}

	public static function Utf8Encode(string: String) {
		var utftext = "";
		var n = 0;
		while (n < string.length) {
			var c = string.charCodeAt(n);
 
			if (c < 128) {
				utftext += String.fromCharCode(c);
			}
			else if((c > 127) && (c < 2048)) {
				utftext += String.fromCharCode((c >> 6) | 192);
				utftext += String.fromCharCode((c & 63) | 128);
			}
			else {
				utftext += String.fromCharCode((c >> 12) | 224);
				utftext += String.fromCharCode(((c >> 6) & 63) | 128);
				utftext += String.fromCharCode((c & 63) | 128);
			}
			n++;
		}
 
		return utftext;
	}
	
	public static function Utf8Decode(utftext : String) {
        var string : String = "";
        var i : Int = 0;
        var c = 0, c1 = 0, c2 = 0;
        while ( i < utftext.length ) {
            c = utftext.charCodeAt(i);
            if (c < 128) {
                string += String.fromCharCode(c);
                i++;
            }
            else if((c > 191) && (c < 224)) {
                c2 = utftext.charCodeAt(i+1);
                string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                i += 2;
            }
            else {
                c2 = utftext.charCodeAt(i+1);
                var c3 = utftext.charCodeAt(i+2);
                string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                i += 3;
            }
        }
        return string;
    }
}